package ac.svn.accesscontrol.manager;

import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.HashSet;
import java.util.Set;

import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import javax.swing.border.MatteBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import ac.notification.event.INotificationListener;
import ac.notification.event.NotificationEvent;
import ac.notification.manager.NotificationRegistrar;
import ac.svn.accesscontrol.controller.ListingController;
import ac.svn.accesscontrol.store.Dictionary;
import ac.svn.accesscontrol.store.GroupDetails;
import ac.svn.accesscontrol.store.IConstants;
import ac.svn.accesscontrol.store.IResources;
import ac.svn.accesscontrol.store.ISVNIdentifier;
import ac.svn.accesscontrol.store.ISVNItem;
import ac.svn.accesscontrol.store.SVNGroup;
import ac.svn.accesscontrol.store.SVNUser;
import ac.svn.accesscontrol.ui.IUIConstants;
import ac.svn.accesscontrol.ui.helper.AccessRightsLister;
import ac.svn.accesscontrol.ui.helper.ContentLister;
import ac.svn.accesscontrol.ui.helper.formatter.GroupItemNameFormatter;
import ac.svn.accesscontrol.ui.model.ThreadedListModel;
import ac.svn.accesscontrol.ui.renderer.SimpleIconRenderer;
import ac.svn.accesscontrol.ui.window.GroupDetailsEditorWindow;
import ac.svn.accesscontrol.ui.window.GroupEditorWindow;
import ac.svn.accesscontrol.utils.UIUtility;
import ac.svn.accesscontrol.utils.Utility;

public class GroupsInformationManager implements INotificationListener ,ISVNIdentifier,IResources
{
	public enum EButtonState
	{
		NO_SELECTION,
		SELECTION_MADE		
	};
	private GroupEditorWindow editorWindow = null;
	private ThreadedListModel defaultListModel = null;
	private GroupDetailsEditorWindow groupDetailsEditorWindow = null;
	private GroupExistenceCheckListener onGroupNameChecker = null;
	private GroupExistenceCheckListener onGroupAliasChecker = null;
	private AccessRightsLister accessRightsLister;
	private ContentLister groupsLister;

	/**
	 * Default Constructor of class.
	 */
	public GroupsInformationManager()
	{
		initComponents();
		registerListerners();
		NotificationRegistrar.registerListener(this);
	}

	private void registerListerners() 
	{

		UIUtility.addPopupListener(editorWindow.getGroupList(),IConstants.VALUE_POPUP_TYPE_GROUP);

		ActionListener exitListener = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				editorWindow.setVisible(false);
			}
		};

		editorWindow.getFinishBtn().addActionListener(exitListener);
		editorWindow.getCloseMI().addActionListener(exitListener); 


		editorWindow.getGroupList().addListSelectionListener(new  ListSelectionListener() {
			@Override
			public void valueChanged(ListSelectionEvent le) 
			{
				//React only when value is selected not when previous one is unselected.
				if(false == le.getValueIsAdjusting())
				{
					ISVNItem svnGroup = (ISVNItem) editorWindow.getGroupList().getSelectedValue();
					if(null == svnGroup)
					{
						changeButtonState(GroupsInformationManager.EButtonState.NO_SELECTION);
					}
					else
					{
						changeButtonState(GroupsInformationManager.EButtonState.SELECTION_MADE);
						accessRightsLister.setGroup(true);
						accessRightsLister.listAccessRights(svnGroup.getId());
					}
				}
			}
		});

		//Add Listener for New button and New menu item.
		ActionListener newUserCreateActionListener = new ActionListener() 
		{
			@Override
			public void actionPerformed(ActionEvent arg0) 
			{
				createNewGroup();
			}
		};

		editorWindow.getNewGroupBtn().addActionListener(newUserCreateActionListener);
		editorWindow.getCreateNewGroupMI().addActionListener(newUserCreateActionListener);

		//Action to be performed when edit button or edit menu is selected.
		ActionListener editGroupActionListener = new ActionListener() 
		{
			@Override
			public void actionPerformed(ActionEvent arg0) 
			{
				Object selectedObject = editorWindow.getGroupList().getSelectedValue();
				if(null != selectedObject)
				{
					SVNGroup svnGroup = (SVNGroup) selectedObject;
					editGroup(svnGroup);
				}
				else
				{
					JOptionPane.showMessageDialog(editorWindow, "Select a group for editing from below list!","No Selection",JOptionPane.INFORMATION_MESSAGE);
				}
			}
		};

		editorWindow.getEditBtn().addActionListener(editGroupActionListener);
		editorWindow.getEditSelectedGroupMI().addActionListener(editGroupActionListener);

		//Action to be performed when edit button or edit menu is selected.
		ActionListener deleteGroupActionListener = new ActionListener() 
		{
			@Override
			public void actionPerformed(ActionEvent arg0) 
			{
				Object selectedObject = editorWindow.getGroupList().getSelectedValue();
				if(null != selectedObject)
				{
					SVNGroup svnGroup = (SVNGroup) selectedObject;
					deleteGroup(svnGroup);
				}
				else
				{
					JOptionPane.showMessageDialog(editorWindow, "Select a group for deletion from below list!","No Selection",JOptionPane.INFORMATION_MESSAGE);
				}
			}
		};

		editorWindow.getDeleteBtn().addActionListener(deleteGroupActionListener);
		editorWindow.getDeleteSelectedGroupMI().addActionListener(deleteGroupActionListener);
		onGroupNameChecker = new GroupExistenceCheckListener(groupDetailsEditorWindow.getGroupNameText(), groupDetailsEditorWindow.getOkButton(),null);
		groupDetailsEditorWindow.getGroupNameText().addFocusListener(onGroupNameChecker);
		groupDetailsEditorWindow.getGroupNameText().addKeyListener(onGroupNameChecker);

		onGroupAliasChecker  = new GroupExistenceCheckListener(groupDetailsEditorWindow.getGroupAliasText(), groupDetailsEditorWindow.getOkButton(),null);
		groupDetailsEditorWindow.getGroupAliasText().addFocusListener(onGroupAliasChecker);
		groupDetailsEditorWindow.getGroupAliasText().addKeyListener(onGroupAliasChecker);

		groupDetailsEditorWindow.getOkButton().addActionListener(new ActionListener() 
		{
			@Override
			public void actionPerformed(ActionEvent arg0) 
			{
				okButtonHandler();
			}
		});

	}

	private void okButtonHandler()
	{
		SwingUtilities.invokeLater(new Runnable()
		{
			@Override
			public void run() 
			{
				Set<ISVNItem> selectedUsers = groupDetailsEditorWindow.getSelectedItems();
				String groupName 	= groupDetailsEditorWindow.getGroupNameText().getText().trim();
				String groupAlias 	  	= groupDetailsEditorWindow.getGroupAliasText().getText().trim();
				String comments		= groupDetailsEditorWindow.getCommentsText().getText().trim();
				String notificationId	= N_GROUP_MODIFIED;
				

				//Check if group exists
				SVNGroup group =SVNInfoManager.getInfoManager().getGroup(groupAlias);
				//If group doesn't exists create a new.
				if(null == group)
				{
					group = new SVNGroup(groupAlias,groupName);
					group.setComments(comments);
					notificationId	= N_GROUP_ADDED;
				}
				else
				{
					group.setComments(comments);
					group.setGroupAlias(groupAlias);
					group.setGroupName(groupName);
				}

				SVNInfoManager infoManager = SVNInfoManager.getInfoManager();
				
				//Remove current membership of group.
				for(String userId: group.getGroupMembers())
				{
					SVNUser svnUser = infoManager.getSvnUser(userId);
					svnUser.getGroups().remove(group.getId());
					
					SVNInfoManager.getInfoManager().updateUser(svnUser);
				}
				
				
				//Clear all existing assignments.
				group.getGroupMembers().clear();

				//Added newly assigned members to group.
				for(ISVNItem user: selectedUsers)
				{
					SVNUser svnUser = infoManager.getSvnUser(user.getId());
					svnUser.addGroup(group.getId());
					
					SVNInfoManager.getInfoManager().updateUser(svnUser);
					
					group.addMember(svnUser.getId());		
				}

				//Update latest state of group in information Manager.
				SVNInfoManager.getInfoManager().updateGroup(group);

				//Update information in Groups Details.
				GroupDetails groupDetails = Dictionary.getDictionary().getGroupDetails().get(group.getId());

				//If information about is not yet saved.
				if(null == groupDetails)
				{
					groupDetails = new GroupDetails(group);
				}
				else
				{
					groupDetails.updateInformation(group);
				}

				//Update information in Config file.
				Dictionary.getDictionary().getGroupDetails().put(group.getId(), groupDetails);

				//Set Details editor invisible.
				groupDetailsEditorWindow.setVisible(false);

				//Send notification for User and Group list update on UI.
				Utility.sendNotification(N_UPDATE_LISTINGS,"USER");
				Utility.sendNotification(N_UPDATE_LISTINGS,"GROUP");
				
				Utility.sendNotification(notificationId);

				populateUserGroups();
			}
		});
	}

	private void initComponents() 
	{
		editorWindow = new GroupEditorWindow();		
		groupDetailsEditorWindow = new GroupDetailsEditorWindow();

		editorWindow.setIconImage(Utility.getImage(IResources.LOC_ICN_GROUP_SMALL));
		groupDetailsEditorWindow.setIconImage(Utility.getImage(IResources.LOC_ICN_GROUP_SMALL));
		
		Utility.addEscapeListener(editorWindow); 
		Utility.addEscapeListener(groupDetailsEditorWindow);

		defaultListModel = new ThreadedListModel();


		editorWindow.getGroupList().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		GroupItemNameFormatter textFormatter = new GroupItemNameFormatter();  
		SimpleIconRenderer iconRenderer = new SimpleIconRenderer(LOC_ICN_GROUP_SMALL);
		iconRenderer.setTextFormatter(textFormatter);
		iconRenderer.setDefBorder(new MatteBorder(1,2,0,0,Color.LIGHT_GRAY)); 
		editorWindow.getGroupList().setCellRenderer(iconRenderer);
		editorWindow.getGroupList().setModel(defaultListModel);

		groupsLister = new ContentLister(editorWindow.getGroupList(), defaultListModel, null);
		groupsLister.setListClrRequired(true); 

		ListingController.getListingController().registerListerForNotifications(groupsLister, IUIConstants.C_LIST_TYPE_GROUP);


		groupDetailsEditorWindow.getAllUsersList().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
		groupDetailsEditorWindow.getAllUsersList().setCellRenderer(new SimpleIconRenderer(LOC_ICN_USER_SMALL));

		groupDetailsEditorWindow.getSelectedUsersList().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
		groupDetailsEditorWindow.getSelectedUsersList().setCellRenderer(new SimpleIconRenderer(LOC_ICN_USER_SMALL));

		accessRightsLister = new AccessRightsLister(editorWindow.getInfoTextPane());
		accessRightsLister.setGroup(true);
		accessRightsLister.setClearRequired(true);

		Font font = new Font("Tahoma",Font.BOLD , 16);
		JLabel titleLabel = editorWindow.getTitleLabel();
		titleLabel.setHorizontalAlignment(JLabel.LEADING);
		titleLabel.setIcon(Utility.getImageIcon(IResources.LOC_ICN_GROUP));
		titleLabel.setFont(font);
		accessRightsLister.setMembershipLabel(titleLabel);

		changeButtonState(EButtonState.NO_SELECTION);
	}

	private void changeButtonState(EButtonState buttonState)
	{
		boolean newGroup = true;
		boolean editGroup = true;
		boolean deleteGroup = true;

		editorWindow.getNewGroupBtn().setEnabled(newGroup);
		editorWindow.getCreateNewGroupMI().setEnabled(newGroup);

		editorWindow.getEditBtn().setEnabled(editGroup);
		editorWindow.getEditSelectedGroupMI().setEnabled(editGroup);

		editorWindow.getDeleteBtn().setEnabled(deleteGroup);
		editorWindow.getDeleteSelectedGroupMI().setEnabled(deleteGroup);
	}

	public void populateUserGroups()
	{
		Set<ISVNItem> groups = SVNInfoManager.getInfoManager().getAllGroups();
		editorWindow.getLblAvailableGroups().setText(String.format(" Available Groups(%d)",groups.size()));
	}


	@Override
	public Set<String> declaredSubscriptionSet() 
	{
		Set<String> subscriptionSet = new HashSet<String>();
		subscriptionSet.add(CMD_SHOW_GROUP_EDITOR);
		subscriptionSet.add(CMD_ADD_GROUP);
		subscriptionSet.add(CMD_EDIT_GROUP);
		subscriptionSet.add(CMD_DELETE_GROUP);
		subscriptionSet.add(N_UPDATE_GROUP_LIST);
		subscriptionSet.add(N_GROUP_REMOVED);
		return subscriptionSet;
	}

	@Override
	public void receiveEvent(final NotificationEvent ne) {
		if(ne.getPropertyIdentifier().contains(CMD_SHOW_GROUP_EDITOR))
		{
			editorWindow.setVisible(true);
			populateUserGroups();					
		}
		else if(ne.getPropertyIdentifier().contains(CMD_ADD_GROUP))
		{
			createNewGroup();
		}
		else if(ne.getPropertyIdentifier().contains(CMD_DELETE_GROUP))
		{
			if(ne.getPropertyValue() instanceof SVNGroup)
			{
				SVNGroup svnGroup =  (SVNGroup) ne.getPropertyValue();
				deleteGroup(svnGroup);				
			}
		}
		else if(ne.getPropertyIdentifier().contains(CMD_EDIT_GROUP))
		{
			if(ne.getPropertyValue() instanceof SVNGroup)
			{
				//Show edit dialog gracefully when group information has been updated 
				//completely on Dialog.
				SwingUtilities.invokeLater(new Runnable() {
					@Override
					public void run() {
						SVNGroup svnGroup =  (SVNGroup) ne.getPropertyValue();
						editGroup(svnGroup);				
					}
				});
			}
			else
			{
				System.err.println(String.format("Encountered unknown type[%s] expecting SVNGroup!",ne.getPropertyValue().getClass().getSimpleName()));
			}
		}
		else if(ne.getPropertyIdentifier().contains(N_UPDATE_GROUP_LIST))
		{
			populateUserGroups();
		}
		else if(ne.getPropertyIdentifier().contains(N_GROUP_REMOVED))
		{
			showDeleteNotification();
		}
	}

	private void showDeleteNotification() {
		SwingUtilities.invokeLater(new Runnable() 
		{
			@Override
			public void run() 
			{
				Object message = "Group has been deleted successfully!";
				JOptionPane.showMessageDialog(editorWindow, message ,"Operation Completed", JOptionPane.INFORMATION_MESSAGE);				
			}
		});
	}

	private void createNewGroup()
	{
		onGroupNameChecker.setExistingValue(null);
		String title = "<HTML><p align=\"CENTER\">" +
				"Welcome to group creation wizard, Mentioned then name of the group and select members to the right" +
				", press Finish to complete group creation!" +
				"</p></HTML>";

		//Clear Data from UI.
		setDataOnUI("","","");

		//Enable text fields for Current operation.
		setEditingState(true, true, true);

		groupDetailsEditorWindow.setItemDescriptionTitle(title, Utility.getImageIcon(IResources.LOC_ICN_EDIT_GROUPS));
		groupDetailsEditorWindow.setAllItem(SVNInfoManager.getInfoManager().getAllUsers());
		groupDetailsEditorWindow.showDialog(null);
	}


	/**
	 *  Method sets specified text to Text fields present on UI.
	 * @param groupName
	 * @param groupAlias
	 * @param comments
	 */
	private void setDataOnUI(String groupName,String groupAlias, String comments)
	{
		groupDetailsEditorWindow.getGroupNameText().setText(groupName);
		groupDetailsEditorWindow.getGroupAliasText().setText(groupAlias);
		groupDetailsEditorWindow.getCommentsText().setText(comments);		
	}


	private void setEditingState(boolean nameText,boolean aliasText, boolean comments)
	{
		groupDetailsEditorWindow.getGroupNameText().setEditable(nameText);
		groupDetailsEditorWindow.getGroupAliasText().setEditable(aliasText);
		groupDetailsEditorWindow.getCommentsText().setEditable(comments);	
	}


	private void editGroup(SVNGroup  svnGroup)
	{
		onGroupNameChecker.setExistingValue(svnGroup.getGroupName());
		String title = String.format("<HTML><p align=\"LEFT\">" +
				"<b>Group Name: </b>%s<br>" +
				"<b>Group Alias: </b>%s<br>"+
				"<b>Members Count: </b>%d<br>"+
				"<b>Group Members: </b>%s<br>"+
				"</p></HTML>", 
				svnGroup.getGroupName(),
				svnGroup.getGroupAlias(),
				svnGroup.getGroupMembers().size(),
				svnGroup.getGroupMembers().toString());
		groupDetailsEditorWindow.setItemDescriptionTitle(title, Utility.getImageIcon(IResources.LOC_ICN_EDIT_GROUPS));
		groupDetailsEditorWindow.setAllItem(SVNInfoManager.getInfoManager().getAllUsers());

		//Set Data On UI.
		setDataOnUI(svnGroup.getGroupName(), svnGroup.getGroupAlias(), svnGroup.getComments());

		//Change Editing State.
		setEditingState(true, false, true);

		Set<ISVNItem> members = new HashSet<ISVNItem>();
		for(String userAlias:svnGroup.getGroupMembers())
		{
			SVNUser svnUser = SVNInfoManager.getInfoManager().getSvnUser(userAlias);
			members.add(svnUser);
		}
		
		groupDetailsEditorWindow.setSelectedItem(members);
		int selectedOption = groupDetailsEditorWindow.showDialog(svnGroup);
		
		//Check if user has pressed finish option.
		if(JOptionPane.YES_OPTION == selectedOption)
		{
			/*
			//Get list of users members of group now.
			Set<ISVNItem> selectedItems = groupDetailsEditorWindow.getSelectedItems();
			
			//Remove old member ship;
			for(String userId : svnGroup.getGroupMembers())
			{
				SVNUser svnUser = SVNInfoManager.getInfoManager().getSvnUser(userId);
				
				//Check if at all user exists.
				if(null != svnUser)
				{
					//Remove membership of group;
					svnUser.getGroups().remove(svnGroup.getId());					
				}
			}
			
			//As group membership is made void, clear member list in Group Object.
			svnGroup.getGroupMembers().clear();
			
			//Now for all new members make membership with user.
			for(ISVNItem user : selectedItems )
			{
				SVNUser svnUser = SVNInfoManager.getInfoManager().getSvnUser(user.getId());
				svnUser.addGroup(svnGroup.getId());
				
				//Update user back to Information Manager.
				SVNInfoManager.getInfoManager().updateUser(svnUser);
				
				//Update Group's membership.
				svnGroup.addMember(svnUser.getId());
			}
			
			//Once membership is updated, update group itself to information manager.
			SVNInfoManager.getInfoManager().updateGroup(svnGroup);
			
			//Relay notification for user and group updates.
			Utility.sendNotification(N_UPDATE_LISTINGS, IUIConstants.C_LIST_TYPE_GROUP);
			Utility.sendNotification(N_UPDATE_LISTINGS, IUIConstants.C_LIST_TYPE_USER);
			*/
		}
	}

	private void deleteGroup(final SVNGroup svnGroup)
	{
		//Show deletion dialog gracefully when group information has been updated 
		//completely on Dialog.
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				if(null != svnGroup)
				{
					int selOption = JOptionPane.showConfirmDialog(editorWindow, 
							String.format("Group \"%s\" will be permanently deleted from SVN, Are you sure you want to delete Group \"%s\"?",svnGroup.getName(),svnGroup.getName()), 
							"Confirm Group Deletion!",
							JOptionPane.YES_NO_OPTION,
							JOptionPane.QUESTION_MESSAGE);
					if(JOptionPane.YES_OPTION == selOption)
					{
						Utility.sendNotification(CMD_DELETE_ENTITY,svnGroup);
					}
					else
					{
						Utility.sendNotification(N_CANCELLED_GROUP_DELETE,svnGroup);
					}
				}
			}
		});
	}

	class GroupExistenceCheckListener implements KeyListener,FocusListener
	{
		private JTextField textField = null;
		private JComponent component = null;
		private String curValue      = null;

		public GroupExistenceCheckListener(JTextField field,JComponent comToDisable,String currentValue)
		{
			this.textField = field;
			this.component = comToDisable;
			this.curValue  = currentValue;
		}
		@Override
		public void focusGained(FocusEvent e) {
			performCheck();
		}
		@Override
		public void focusLost(FocusEvent e) {}
		@Override
		public void keyPressed(KeyEvent e) {}
		@Override
		public void keyReleased(KeyEvent e){
			performCheck();
		}
		@Override
		public void keyTyped(KeyEvent e){}

		private void performCheck()
		{
			String groupName = textField.getText();
			if((null!= groupName) && (false == groupName.isEmpty()))
			{
				if(true == SVNInfoManager.getInfoManager().isExistingGroup(groupName))
				{
					//Following check is performed.
					//1. if group creation is in progress, existence of same group is checked.
					//2. If editing is on existence of another group is checked.

					if(null == curValue)
					{
						component.setEnabled(false);
						textField.setBorder(new LineBorder(Color.RED));
						textField.setToolTipText("Group with same name already exists!");
					}
					else
					{
						if(false == groupName.equalsIgnoreCase(curValue))
						{
							component.setEnabled(false);
							textField.setBorder(new LineBorder(Color.RED));
							textField.setToolTipText("Group with same name already exists!");
						}
						else
						{
							component.setEnabled(true);
							textField.setBorder(new LineBorder(Color.GREEN));
							textField.setToolTipText("Group can be updated!");
						}
					}
				}
				else
				{
					component.setEnabled(true);
					textField.setBorder(new LineBorder(Color.GREEN));
					textField.setToolTipText("Group can be created with this name!");
				}
			}
			else
			{
				component.setEnabled(false);
				textField.setBorder(new LineBorder(Color.RED));
				textField.setToolTipText("Group name is mandatory!");
			}

		}

		public void setExistingValue(String currentValue)
		{
			this.curValue = currentValue;
		}
	}
}